/**
 * 2015，thor.com，all rights reserved。
 * 
 * Project：		thor-common
 * Filename：	SQLSubquery.java
 * Created：		2015年3月14日
 * Creator：		thor
 */
package com.jeecg.query;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jeecg.common.Assert;



/**
 * 查询
 * 
 * @author Debenson
 * @since 0.1
 */
@SuppressWarnings("rawtypes")
public class SQLSubquery {
  private List<String> selectClauses = new ArrayList<String>();
  private Class masterEntityClass;
  private String masterEntityAlias;
  private List<JoinEntity> joinEntities = new ArrayList<JoinEntity>();
  private List<String> whereClause = new ArrayList<String>();
  private List<String> orderClause = new ArrayList<String>();
  private Map<String, Object> parameters = new HashMap<String, Object>();

  public SQLSubquery() {
  }

  public SQLSubquery(Class masterEntityClass, String masterEntityAlias) {
    this();
    setMasterEntity(masterEntityClass, masterEntityAlias);
  }

  public void setMasterEntity(Class masterEntityClass, String masterEntityAlias) {
    setMasterEntityClass(masterEntityClass);
    setMasterEntityAlias(masterEntityAlias);
  }

  public Class getMasterEntityClass() {
    return masterEntityClass;
  }

  public void setMasterEntityClass(Class masterEntityClass) {
    this.masterEntityClass = masterEntityClass;
  }

  public String getMasterEntityAlias() {
    return masterEntityAlias;
  }

  public void setMasterEntityAlias(String masterEntityAlias) {
    this.masterEntityAlias = masterEntityAlias;
  }

  public Map<String, Object> getParameters() {
    return parameters;
  }

  public void addSelect(String... fields) {
    for (String field : fields)
      selectClauses.add(field);
  }

  public void addJoinEntity(JoinEntity joinEntity) {
    joinEntities.add(joinEntity);
  }

  public void addJoinEntity(Class joinEntityClass, String joinEntityAlias, String joinHQL) {
    joinEntities.add(new JoinEntity(joinEntityClass, joinEntityAlias, joinHQL));
  }

  private static final Pattern namedParameterPattern = Pattern.compile(":(\\w+)");

  /**
   * 添加命名参数的查询条件。
   * 
   * @param condition
   *          查询条件， 参数必须是命名参数，如： o.uuid = :uuid and ...
   * @param parameterValue
   *          命名参数值，必须与命名参数相对应。
   */
  public void addNamedCondition(String condition, Object... parameterValue) {
    Assert.assertArgumentNotEmpty(condition, "condition");

    Matcher m = namedParameterPattern.matcher(condition);
    int count = 0;
    while (m.find()) {
      String key = m.group(1);
      if (parameters.containsKey(key)) {
        throw new RuntimeException("参数 " + key + " 已经存在");
      }
      parameters.put(key, parameterValue[count++]);
    }
    whereClause.add(condition);
  }

  /**
   * 添加查询条件。
   *
   * <p>
   * 与 {@link #addNamedCondition(String, Object...)}不同的是，
   * 它支持参数?号占位，实际执行时?将被替换成生成的参数名。
   *
   * @param condition
   *          查询条件， 参数必须是命名参数，如： o.uuid = ? and ...
   * @param parameterValues
   *          命名参数值，必须与参数个数相等。
   */
  public void addCondition(String condition, Object... parameterValues) {
    Assert.assertArgumentNotEmpty(condition, "condition");

    int count = 0;
    while (condition.indexOf("?") >= 0) {
      final String pk = nextParamKey();
      if (parameterValues == null || parameterValues.length <= count) {
        throw new IllegalArgumentException("参数不足，请检查sql");
      }
      condition = condition.replaceFirst("\\?", ":" + pk);
      parameters.put(pk, parameterValues[count++]);
    }
    whereClause.add(condition);
  }

  public void addOrder(String order, QueryOrderDirection direction) {
    orderClause.add(order + (QueryOrderDirection.desc.equals(direction) ? " desc " : " asc "));
  }

  public String toSQL() {
    StringBuffer sb = new StringBuffer();
    sb.append(buildSelect());
    sb.append(buildFrom());
    sb.append(buildWhere());
    sb.append(buildOrder());
    return sb.toString();
  }

  public String toCountSQL() {
    StringBuffer sb = new StringBuffer();
    sb.append("select count(*) ");
    sb.append(buildFrom());
    sb.append(buildWhere());
    return sb.toString();
  }

  private StringBuffer buildSelect() {
    StringBuffer sb = new StringBuffer();
    if (selectClauses.isEmpty())
      return sb;
    for (String field : selectClauses) {
      if (sb.length() > 0) {
        sb.append(", ");
      }
      sb.append(field);
    }
    sb.insert(0, "select ").append(" ");
    return sb;
  }

  private StringBuffer buildFrom() {
    StringBuffer sb = new StringBuffer();
    sb.append("from " + masterEntityClass.getName() + " " + masterEntityAlias + " ");
    for (JoinEntity joinEntity : joinEntities) {
      sb.append(", " + joinEntity.getJoinEntityClass().getName() + " "
          + joinEntity.getJoinEntityAlias() + " ");
    }
    return sb;
  }

  private StringBuffer buildWhere() {
    StringBuffer sb = new StringBuffer();
    if (joinEntities.isEmpty() && whereClause.isEmpty())
      return sb;
    for (JoinEntity joinEntity : joinEntities) {
      if (sb.length() > 0) {
        sb.append(" and ");
      }
      sb.append(joinEntity.getJoinHQL());
    }
    for (String condition : whereClause) {
      if (sb.length() > 0)
        sb.append(" and ");
      sb.append("(" + condition + ")");
    }
    sb.append(" ");
    sb.insert(0, "where ");
    return sb;
  }

  private StringBuffer buildOrder() {
    StringBuffer sb = new StringBuffer();
    if (orderClause.isEmpty())
      return sb;
    for (String order : orderClause) {
      if (sb.length() > 0)
        sb.append(", ");
      sb.append(order);
    }
    sb.insert(0, "order by ");
    return sb;
  }

  private String nextParamKey() {
    return "_param" + parameters.size();
  }
}